/*
 * Created by SharpDevelop.
 * User: Edward Wilde (http://blogs.charteris.com/edwardw)
 * Date: 01/07/2007
 * Time: 18:31
 * 
 */

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Runtime.Serialization;
using WatiN.Core;

namespace Wax
{
    /// <summary>
    /// The test manager class is responsible for executing all of the <see cref="TestManager.Tests" /> that comprise of this test run.
    /// </summary>
    public class TestManager : IDisposable, ITestManager
    {

        #region Private fields

        /// <summary>
        /// Browser used to carry out the <see cref="TestManager.Tests">Tests</see>.
        /// </summary>
        WatiN.Core.IE browser;

        /// <summary>
        /// A list of all the tests that comprise this <see cref="TestManager" />.
        /// </summary>
        private readonly Collection<Test> tests;

        /// <summary>
        /// If <c>true</c> the object's disposed method has already been called.
        /// </summary>
        private bool isDisposed;

        /// <summary>
        /// If <c>true</c> will throw any errors encountered during the execution of the <see cref="Tests"/>. If <c>false</c> will
        /// traps any errors encountered during the execution of the <see cref="Tests"/> and make those exceptions available via
        /// <see cref="Test.Errors"/>. The default value is set to <c>true</c>, errors will be thrown.
        /// </summary>
        private bool throwErrors;

        /// <summary>
        /// Used to store the total number of commands to be executed
        /// </summary>
        private int totalNumberOfCommands;

        /// <summary>
        /// The number of commands successfully completed.
        /// </summary>
        private int numberOfCompletedCommands;

        /// <summary>
        /// Number of failed commands
        /// </summary>
        private int numberOfFailedCommands;

        /// <summary>
        /// The execution context.
        /// </summary>
        private readonly IExecutionContext executionContext;

        #endregion

        #region Constructors

        /// <summary>
        /// Creates a new instance of the <see cref="TestManager" /> class.
        /// </summary>
        public TestManager()
        {
            IE.Settings.AutoMoveMousePointerToTopLeft = false;
            this.tests = new Collection<Test>();
            this.throwErrors = true;
            this.executionContext = new ExecutionContext(this);
        }

        #endregion

        #region Public events

        /// <summary>
        /// Occurs when the test execution has started.
        /// </summary>
        public event EventHandler TestStarted;

        /// <summary>
        /// Occurs when execution has completed.
        /// </summary>
        public event EventHandler TestCompleted;

        /// <summary>
        /// Occurs when a <see cref="Tests">test</see> throws an error.
        /// </summary>
        public event EventHandler<TestErrorEventArgs> TestErrorOccurred;

        /// <summary>
        /// Occurs when progress is made, each time a command has been successfully executed.
        /// </summary>
        public event EventHandler<TestProgressEventArgs> Progress;

        /// <summary>
        /// Occurs when a command completes.
        /// </summary>
        public event EventHandler<CommandCompletedEventArgs> CommandCompleted;

        #endregion

        #region Public properties

        /// <summary>
        /// A list of all the commands that comprise this <see cref="Test" />.
        /// </summary>
        public Collection<Test> Tests
        {
            get { return this.tests; }
        }

        /// <summary>
        /// Gets the execution context.
        /// </summary>
        /// <value>The execution context.</value>
        [CLSCompliant(false)]
        public IExecutionContext ExecutionContext
        {
            get { return this.executionContext; }
        }

        /// <summary>
        /// Browser used to carry out the <see cref="TestManager.Tests">Tests</see>.
        /// </summary>
        [CLSCompliant(false)]
        public WatiN.Core.IE Browser
        {
            get
            {
                if (this.browser == null)
                {
                    this.browser = new WatiN.Core.IE();
                }

                return this.browser;
            }

            set
            {
                this.browser = value;
            }
        }

        /// <summary>
        /// If <c>true</c> will throw any errors encountered during the execution of the <see cref="Tests"/>. If <c>false</c> will
        /// traps any errors encountered during the execution of the <see cref="Tests"/> and make those exceptions available via
        /// <see cref="Test.Errors"/>. The default value is set to <c>true</c>, errors will be thrown.
        /// </summary>
        public bool ThrowErrors
        {
            get { return throwErrors; }
            set { throwErrors = value; }
        }

        #endregion

        #region Public methods

        /// <summary>
        /// Execute all of the <see cref="TestManager.Tests">Tests</see>.
        /// </summary>
        public void Execute()
        {           
            PreExecuteInitialize();

            foreach (Test test in this.Tests)
            {
                if (this.TestStarted != null)
                {
                    this.TestStarted(test, EventArgs.Empty);
                }

                test.Execute(executionContext, this.ThrowErrors);

                if (this.TestCompleted != null)
                {
                    this.TestCompleted(test, EventArgs.Empty);
                }
            }
        }

        #region IDisposable methods

        /// <summary>
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// <see cref="TestManager"/> is reclaimed by garbage collection.
        /// </summary>
        ~TestManager()
        {
            Dispose(false);
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);

            // tell the GC that the Finalize process no longer needs
            // to be run for this object.
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="isDisposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        protected virtual void Dispose(bool isDisposing)
        {
            if (this.isDisposed)
            {
                return;
            }

            if (isDisposing)
            {
                if (this.browser != null)
                {
                    this.browser.Dispose();
                }

                foreach (Test test in tests)
                {
                    test.ErrorOccurred -= new EventHandler<TestErrorEventArgs>(OnTestErrorOccured);                 
                    test.CommandCompleted -= new EventHandler<CommandCompletedEventArgs>(OnCommandCompleted);
                }
            }

            this.isDisposed = true;
        }

        #endregion

        #endregion

        #region Private instance methods

        /// <summary>
        /// Initializes test objects prior to execution.
        /// </summary>
        private void PreExecuteInitialize()
        {
            this.numberOfCompletedCommands = 0;
            this.numberOfFailedCommands = 0;

            foreach (Test test in this.Tests)
            {
                this.totalNumberOfCommands = this.totalNumberOfCommands + test.Commands.Count;
                test.ErrorOccurred += new EventHandler<TestErrorEventArgs>(OnTestErrorOccured);
                test.CommandCompleted += new EventHandler<CommandCompletedEventArgs>(OnCommandCompleted); 
            }
        }

        /// <summary>
        /// Handles the CommandCompleted event of the test control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void OnCommandCompleted(object sender, CommandCompletedEventArgs e)
        {
            if (e.Success)
            {
                this.numberOfCompletedCommands = this.numberOfCompletedCommands + 1;
            }
            else
            {
                this.numberOfFailedCommands = this.numberOfFailedCommands + 1;
            }

            if (this.Progress != null)
            {
                this.Progress(this, new TestProgressEventArgs(this.totalNumberOfCommands, this.numberOfCompletedCommands));   
            }

            if (this.CommandCompleted != null)
            {
                this.CommandCompleted(sender, new CommandCompletedEventArgs(this.numberOfCompletedCommands, this.numberOfFailedCommands, e.Success));
            }
        }

        /// <summary>
        /// Called when a test error occurs.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void OnTestErrorOccured(object sender, TestErrorEventArgs e)
        {
            if (this.TestErrorOccurred != null)
            {
                this.TestErrorOccurred(sender, e);
            }
        }

        #endregion
    }
}
